package es.us.idinfor;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import es.us.idinfor.util.Util;

public class Sequence {
	private static Function<String, Sequence> expression = new StringToSequence();
	
	private String id;
	private long timeStampStart;
	private long timeStampEnd;
	private List<Event> events;
	
	public Sequence(String name, long milisecondsStart, long milisecondsEnd) {
		id = name;
		events = Lists.newArrayList();
		timeStampStart = milisecondsStart;
		timeStampEnd = milisecondsEnd;
	}
	
	public String getId() {
		return id;
	}
	
	public long getTimeStampStart() {
		return timeStampStart;
	}
	
	public long getTimeStampEnd() {
		return timeStampEnd;
	}
	
	public List<Event> getEvents() {
		return events;
	}
	
	public boolean add(Event e) {
		boolean res = false;
		
		if(((getTimeStampStart() - e.getTimeStamp()) <= 0) && ((e.getTimeStamp() - getTimeStampEnd()) < 0)) {
			res = getEvents().add(e);
			
			if(res) {
				Collections.sort(getEvents());
			}
		}
		
		return res;
	}
	
	public boolean isEmpty() {
		return getEvents().isEmpty();
	}
	
	public int size() {
		long s = getTimeStampStart();
		long e = getTimeStampEnd();
		
		return ((int) ((e - s) / 1000)); 
	}
	
	public Window window(String name, long start, long end) {
		Window res = new Window(name, start, end);
		
		for(Event e: getEvents()) {
			if(((start - e.getTimeStamp()) <= 0) && ((e.getTimeStamp() - end) < 0)) {
				res.add(e);
			}
		}
		
		return res;
	}
	
	public Set<Window> windows(String name, int seconds) {
		Set<Window> res = Sets.newHashSet();
		Calendar c = Calendar.getInstance();
		long start = 0;
		long end = 0;
		int index = 1;
		
		c.setTimeInMillis(getTimeStampStart());
		c.add(Calendar.SECOND, 1 - seconds);
		start = c.getTimeInMillis();
		
		c.setTimeInMillis(getTimeStampStart());
		c.add(Calendar.SECOND, 1);
		end = c.getTimeInMillis();
		
		while((start - getTimeStampEnd()) < 0) {
			res.add(window(name + index + " (" + getId() + ")", start, end));
			
			c.setTimeInMillis(start);
			c.add(Calendar.SECOND, 1);
			start = c.getTimeInMillis();
			
			c.setTimeInMillis(end);
			c.add(Calendar.SECOND, 1);
			end = c.getTimeInMillis();
			
			index++;
		}
		
		c = null;
		
		return res;
	}
	
	public static Function<String, Sequence> getExpression() {
		return expression;
	}
	
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		
		result = prime * result + (int) (getTimeStampStart() ^ (getTimeStampStart() >>> 32));
		result = prime * result + (int) (getTimeStampEnd() ^ (getTimeStampEnd() >>> 32));
		result = prime * result + ((getEvents() == null) ? 0 : getEvents().hashCode());
		
		return result;
	}
	
	public boolean equals(Object obj) {
		if(this == obj) {
			return true;
		}
		
		if(obj == null) {
			return false;
		}
		
		if(getClass() != obj.getClass()) {
			return false;
		}
		
		Sequence other = (Sequence) obj;
		
		if(getTimeStampStart() != other.getTimeStampStart()) {
			return false;
		}
		
		if(getTimeStampEnd() != other.getTimeStampEnd()) {
			return false;
		}
		
		if(getEvents() == null) {
			if(other.getEvents() != null) {
				return false;
			}
		}
		else if(!getEvents().equals(other.getEvents())) {
			return false;
		}
		
		return true;
	}

	public String toString() {
		String res = getId() + " = <";
		
		for(Event e: getEvents()) {
			res += e.toString() + ", ";
		}
		
		if(!isEmpty()) {
			res = res.substring(0, res.length() - 2);
		}
		
		res += ">";
		
		return res;
	}
	
	private static class StringToSequence implements Function<String, Sequence> {
		public Sequence apply(String s) {
			Sequence res = null;
			String idSequence = "";
			String idEvent = "";
			String timeStampEvent = "";
			List<Event> events = Lists.newArrayList();
			List<String> aux1 = null;
			List<String> aux2 = null;
			
			// Tratamos la cadena que representa a la secuencia
			aux1 = Util.splitElements(s, "=");
			
			if(aux1.size() != 2) {
				throw new IllegalArgumentException("Error in the number of parameters in sequence.");
			}
			
			idSequence = aux1.get(0);
			
			// Tratamos la cadena que representa a los eventos
			String cadEvents = aux1.get(1).substring(1, aux1.get(1).length() - 1);
			
			aux1 = Util.splitElements(cadEvents, ",");
			
			if(aux1.size() < 2) {
				throw new IllegalArgumentException("Error in the number of parameters in events.");
			}
			
			for(String cad: aux1) {
				
				// Tratamos la cadena que representa al evento
				aux2 = Util.splitElements(cad, "-");
				
				if(aux2.size() != 2) {
					throw new IllegalArgumentException("Error in the number of parameters in event.");
				}
				
				try {
					idEvent = aux2.get(0).substring(1);
					timeStampEvent = aux2.get(1).substring(0, aux2.get(1).length() - 1);
					
					events.add(new Event(idEvent, timeStampEvent));
				} catch (ParseException e1) {
					throw new IllegalArgumentException("Error in the format of event time.");
				}
			}
			
			res = new Sequence(idSequence, events.get(0).getTimeStamp(), events.get(events.size() - 1).getTimeStamp() + 1000);
			
			for(Event e: events) {
				res.add(e);
			}
			
			timeStampEvent = null;
			idEvent = null;
			idSequence = null;
			events = null;
			aux2 = null;
			aux1 = null;
			
			return res;
		}
	}
}
